package com.qiantoon.networkdsl.http

import android.text.TextUtils
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.blankj.utilcode.util.ToastUtils
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.qiantoon.networkdsl.base.BaseNetworkApi
import com.qiantoon.networkdsl.base.INetWorkOp
import com.qiantoon.networkdsl.errorhandler.ExceptionHandle
import com.qiantoon.networkdsl.support.EmptyUtils
import com.qiantoon.networkdsl.support.PreferencesUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.lang.reflect.Type
import java.net.SocketTimeoutException
import java.net.UnknownHostException


class Request<T> {

    companion object {

        private val gson = Gson()

        /*** Token失效 */
        private var onTokenInvalid: (() -> Unit)? = null

        /**系统维护中*/
        private var onSystemMaintenance: (() -> Unit)? = null

        var networkOp: INetWorkOp? = null

        infix fun onTokenInvalid(onTokenInvalid: (() -> Unit)?) {
            this.onTokenInvalid = onTokenInvalid
        }

        infix fun onSystemMaintenance(onSystemMaintenance: (() -> Unit)?) {
            this.onSystemMaintenance = onSystemMaintenance
        }
    }

    private lateinit var loader: suspend () -> BaseResponseBean

    private var start: (() -> Unit)? = null

    private var onSuccess: ((T) -> Unit)? = null

    private var onError: ((Throwable) -> Boolean)? = null

    private var onComplete: (() -> Unit)? = null

    private var type:(()->Type)? = null

    infix fun type(type:(()->Type)?){
        this.type = type
    }

    infix fun loader(loader: suspend () -> BaseResponseBean) {
        this.loader = loader
    }

    infix fun start(start: (() -> Unit)?) {
        this.start = start
    }

    infix fun onSuccess(onSuccess: ((T) -> Unit)?) {
        this.onSuccess = onSuccess
    }

    infix fun onError(onError: ((Throwable) -> Boolean)?) {
        this.onError = onError
    }

    infix fun onComplete(onComplete: (() -> Unit)?) {
        this.onComplete = onComplete
    }

    lateinit var scope: CoroutineScope

    fun request(scope: CoroutineScope) {
        if(type == null){
            throw NullPointerException("type must use")
        }
        this.scope = scope
        scope.launch {
            if (EmptyUtils.isEmpty(BaseNetworkApi.getAuth_token())) {
                checkToken(scope)
            } else {
                startRequest()
            }
        }
    }

    private suspend fun startRequest() {
        start?.invoke()
        try {

            val bean = loader()
            val typeToken = object : TypeToken<T>() {}

            when (bean.head.resultCode) {
                BaseNetworkApi.RESPONSE_CODE_SUCCESS -> {
                    if (bean.data == null) {
                        val data: T = gson.fromJson("{}", typeToken.type)
                        onSuccess?.invoke(data)
                        return
                    }
//                    val data = bean.getDecryptDataByType<T>(typeToken.type)
                    val data = bean.getDecryptDataByType<T>(type?.invoke()!!)
                    if (data == null) {
                        onError?.invoke(RuntimeException("Data decryption exception"))
                    } else {
                        onSuccess?.invoke(data)
                    }
                }
                BaseNetworkApi.RESPONSE_CODE_NO_DATA -> {
                    val data: T = gson.fromJson("[]", typeToken.type)
                    onSuccess?.invoke(data)
                }
                else -> {
                    appErrorHandler(bean)
                }
            }
        } catch (e: Throwable) {
            when (e) {
                is UnknownHostException -> onError?.invoke(RuntimeException("network is error!"))
                is SocketTimeoutException -> onError?.invoke(RuntimeException("network is error!"))
                else -> {
                    val ex = ExceptionHandle.handleException(e)
                    handleErrorHandle(ex)
                }
            }
        } finally {
            onComplete?.invoke()
        }
    }

    private suspend fun getToken(): BaseResponseBean {
        if (networkOp != null) {
            return networkOp!!.getToken()
        }
        throw NullPointerException("请初始化INetWorkOp接口")
    }

    private fun appErrorHandler(response: BaseResponseBean) {
        val head = response.head
        if ((TextUtils.equals(head.resultCode, BaseNetworkApi.RESPONSE_CODE_ERROR) &&
                        head.errMsg.contains("验证token值失败")) ||
                TextUtils.equals(head.resultCode, BaseNetworkApi.RESPONSE_CODE_APP_TOKEN_ERROR) ||
                TextUtils.equals(head.resultCode, BaseNetworkApi.RESPONSE_CODE_APP_TOKEN_INVALID)) {
            BaseNetworkApi.setAuth_token(null)
            request(scope)
            return
        } else if (TextUtils.equals(head.resultCode, BaseNetworkApi.RESPONSE_CODE_USER_TOKEN_INVALID) ||
                TextUtils.equals(head.resultCode, BaseNetworkApi.RESPONSE_CODE_KICK)) {
            /*** 104	登陆Token失效, 105	账号被挤下线 */
            PreferencesUtil.getInstance().setUserInfo(null)
            if (TextUtils.equals(head.resultCode, BaseNetworkApi.RESPONSE_CODE_KICK)) {
                networkOp?.handle105()
            }
        }
        // 卡管信息更新，需要重新获取用户信息
        else if (BaseNetworkApi.RESPONSE_CODE_HEALTHY_CARD_ID_REFRESH == head.resultCode) {
            networkOp?.handle202()
        }

        val exception: ExceptionHandle.ServerException = ExceptionHandle.ServerException()
        exception.code = response.head.resultCode
        exception.errorMessage = response.head.errMsg ?: ""

        throw exception

    }

    private fun handleErrorHandle(responseThrowable: ExceptionHandle.ResponseThrowable) {
        responseThrowable.printStackTrace()
        if (TextUtils.equals(responseThrowable.code, BaseNetworkApi.RESPONSE_CODE_SYSTEM_MAINTENANCE)) {
            onSystemMaintenance?.invoke()
            return
        }

        if (TextUtils.equals(responseThrowable.code, BaseNetworkApi.RESPONSE_CODE_USER_TOKEN_INVALID)
                /*** 登陆Token失效  */
                /*** 登陆Token失效  */
                || TextUtils.equals(responseThrowable.code, BaseNetworkApi.RESPONSE_CODE_KICK)) {
            /*** 账号被挤下线  */
            onTokenInvalid?.invoke()
            return
        }

        if (onError?.let { it(responseThrowable) }!!) {
            return
        }

        if (TextUtils.equals(responseThrowable.code, BaseNetworkApi.RESPONSE_CODE_NO_DATA)) { //没有查询到数据信息,不toast
        } else {
            ToastUtils.showShort(responseThrowable.errorMessage)
        }
    }


    private fun checkToken(scope: CoroutineScope) {
        scope.launch {
            try {
                val bean = getToken()
                Log.e("bean====>", bean.toString())
                if (bean.head.resultCode == BaseNetworkApi.RESPONSE_CODE_SUCCESS ||
                        bean.head.resultCode == BaseNetworkApi.RESPONSE_CODE_NO_DATA) {
                    val data = bean.getDecryptDataByType<TokenBean>(object : TypeToken<TokenBean>() {}.type)
                    if (data == null) {
                        onError?.invoke(RuntimeException("Data decryption exception"))
                    } else {
                        Log.e("token====>", data.Token)
                        BaseNetworkApi.setAuth_token(data.Token)
                        startRequest()
                    }
                } else {
                    appErrorHandler(bean)
                }
            } catch (e: Exception) {
                e.printStackTrace()
                when (e) {
                    is UnknownHostException -> onError?.invoke(RuntimeException("network is error!"))
                    is SocketTimeoutException -> onError?.invoke(RuntimeException("network is error!"))
                    else -> {
                        val ex = ExceptionHandle.handleException(e)
                        handleErrorHandle(ex)
                    }
                }
            }
        }
    }

}


inline fun <T> CoroutineScope.request(buildRequest: Request<T>.() -> Unit) {
    Request<T>().apply(buildRequest).request(this)
}

inline fun <T> ViewModel.request(buildRequest: Request<T>.() -> Unit) {
    Request<T>().apply(buildRequest).request(this.viewModelScope)
}

